package cn.simon.free.filter.core;

import lombok.extern.slf4j.Slf4j;
import org.slf4j.MDC;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.Enumeration;
import java.util.Optional;
import java.util.UUID;

/**
 * 日志打印过滤器核心实现
 *
 * @author simon
 * @date 2020/5/29 10:06
 * @copyright:
 * @modifier
 * @motto a no talent, people who have to study hard
 */
@Slf4j
public class FilterLogCore implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        log.info("The filter of params printf init over.");
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        ReaderRequestWrapper readerRequestWrapper = new ReaderRequestWrapper(httpServletRequest);
        MDC.put("X-B3-TraceId", "DL".concat(UUID.randomUUID().toString()));
        log.debug("once request start.");
        requestLog(readerRequestWrapper);
        chain.doFilter(readerRequestWrapper, response);
        log.debug("once request end.");
    }

    private void requestLog(ReaderRequestWrapper httpServletRequest) {
        String url = httpServletRequest.getRequestURL().toString();
        String method = httpServletRequest.getMethod();
        logExtra(httpServletRequest, url, method);
        logHeads(httpServletRequest, url, method);
        logParameters(httpServletRequest, url, method);
        logBody(httpServletRequest, url, method);
        log.info("[url] {}, [method] {}, [body] {}", url, method, httpServletRequest.body);
    }

    /**
     * printf extra
     * e.g remoteAddressIp
     *
     * @param httpServletRequest
     * @param url
     * @param method
     */
    private void logExtra(ReaderRequestWrapper httpServletRequest, String url, String method) {
        log.debug("[url] {}, [method] {}, [extra] {}", url, method, httpServletRequest.getRemoteAddr());
    }

    /**
     * printf header
     *
     * @param httpServletRequest
     * @param url
     * @param method
     */
    private void logHeads(HttpServletRequest httpServletRequest, String url, String method) {
        Enumeration<String> headerNames = httpServletRequest.getHeaderNames();
        while (headerNames.hasMoreElements()) {
            String header = headerNames.nextElement();
            log.debug("[url] {}, [method] {}, [header] {} = {}", url, method, header, httpServletRequest.getHeader(header));
        }
    }

    /**
     * printf parameter
     *
     * @param httpServletRequest
     * @param url
     * @param method
     */
    private void logParameters(HttpServletRequest httpServletRequest, String url, String method) {
        Enumeration<String> parameterNames = httpServletRequest.getParameterNames();
        while (parameterNames.hasMoreElements()) {
            String param = parameterNames.nextElement();
            log.debug("[url] {}, [method] {}, [param] {} = {}", url, method, param, httpServletRequest.getParameter(param));
        }
    }

    /**
     * printf body
     * remind : httpServletRequest.getInputStream() 取值后,需要在赋值回去.
     *
     * @param readerRequestWrapper
     * @param url
     * @param method
     */
    private void logBody(ReaderRequestWrapper readerRequestWrapper, String url, String method) {
        if (!isMultipart(readerRequestWrapper)) {
            log.debug("[url] {}, [method] {}, [body] {}", url, method, readerRequestWrapper.body);
        }
    }


    /**
     * 判断是否为文件上传请求
     * true : 文件上传类型请求
     * false : 其他类型请求(e.g : application/json;charset=UTF-8)
     *
     * @return
     */
    public boolean isMultipart(HttpServletRequest httpServletRequest) {
        return Optional.ofNullable(httpServletRequest.getContentType()).filter(c -> c.toLowerCase().startsWith("multipart")).isPresent();
    }

    class ReaderRequestWrapper extends HttpServletRequestWrapper {
        private String body;

        /**
         * Constructs a request object wrapping the given request.
         *
         * @param request The request to wrap
         * @throws IllegalArgumentException if the request is null
         */
        public ReaderRequestWrapper(HttpServletRequest request) {
            super(request);
            try {
                ServletInputStream inputStream = request.getInputStream();
                int len;
                byte[] buf = new byte[2048];
                ByteArrayOutputStream bos = new ByteArrayOutputStream();
                while ((len = inputStream.read(buf)) != -1) {
                    bos.write(buf, 0, len);
                }
                body = bos.toString(Charset.defaultCharset().name()).replaceAll("\t", "").replaceAll("\n", "");
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        @Override
        public ServletInputStream getInputStream() throws IOException {
            ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(body.getBytes());
            ServletInputStream servletInputStream = new ServletInputStream() {
                @Override
                public boolean isFinished() {
                    return false;
                }

                @Override
                public boolean isReady() {
                    return false;
                }

                @Override
                public void setReadListener(ReadListener listener) {

                }

                @Override
                public int read() throws IOException {
                    return byteArrayInputStream.read();
                }
            };
            return servletInputStream;
        }
    }
}
